package org.sgx.gwteditors.client.impl1.complex;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.sgx.gwteditors.client.editor.ConfigurableEditor;
import org.sgx.gwteditors.client.editor.Editor;
import org.sgx.gwteditors.client.editor.EditorNotFound;
import org.sgx.gwteditors.client.editor.Types;
import org.sgx.gwteditors.client.editor.error.EditorError;
import org.sgx.gwteditors.client.editor.error.EditorErrorManager;
import org.sgx.gwteditors.client.editor.event.EditorEventManager;
import org.sgx.gwteditors.client.editor.event.EditorValueChangeEvent;
import org.sgx.gwteditors.client.editor.event.ValueChangeListener;
import org.sgx.gwteditors.client.editor.props.PropertyHaver;
import org.sgx.gwteditors.client.editor.props.PropertyInfo;
import org.sgx.gwteditors.client.impl1.EditorFramework1;
import org.sgx.gwteditors.client.impl1.EditorManager1;
import org.sgx.gwteditors.client.util.GUIUtil;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * just like PHE1 but supporting anidated complex editors. complex properties will 
 * be showed in a label, than when clicked will render the sub-complex editor in a dialog.
 * 
 * @author sg
 * 
 * @param <T>
 *            the PropertyHaver class to edit
 */
public class PropertyHaverEditor2<T extends PropertyHaver> extends FlexTable implements Editor<T>,
		ValueChangeListener<Object> {

	static Logger logger = Logger.getLogger(PropertyHaverEditor2.class + "");

	@Override
	public boolean isSimple() {
		return false;
	}

	private Map<String, Editor<Object>> editors;
	protected T model = null;
	protected DialogBox currentDialogBox;

	public PropertyHaverEditor2() {
		super();
		editors = new HashMap<String, Editor<Object>>();
	}

	@Override
	public void addValueChangeListener(ValueChangeListener<T> l) {
		EditorEventManager.<T> getInstance().addValueChangeListener(this, l);
	}

	@Override
	public List<EditorError<T>> getErrors() {
		return EditorErrorManager.<T> getInstance().getErrors(this);
	}

	@Override
	public void removeValueChangeListener(ValueChangeListener<T> l) {
		EditorEventManager.<T> getInstance().removeValueChangeListener(this, l);
	}

	@Override
	public T flush() {
		if (model == null) {
			logger.log(Level.WARNING, "first load() must be called and then flush()");
			return null;
		}
		T obj = model; // we modify the passed PH !
		PropertyHaverEditorError errors = new PropertyHaverEditorError(this);
		boolean error = false;
		for (String propName : editors.keySet()) {
			Editor ed = editors.get(propName);

			if (ed != null) {
				Object val = ed.flush();
				if (val == null && ed.getErrors().size() > 0) {
					error = true;
					errors.putFieldError(propName, ed.getErrors());
				} else {
					obj.propertySet(propName, val);
				}
			}
		}
		if (error) {
			EditorErrorManager.<PropertyHaver> getInstance().setError(this, errors);
			return null;
		} else {
			return obj;
		}
	}

	@Override
	public void load(T model) {
		if (model == null)
			return;
		this.model = model;

		clear();// clear gui
		editors.clear();// clear model
		removeMySelfAsListener();

		List<String> props = model.getProperties().keys();
		int i = 0;
		for (String name : props) {

			final String name2 = name;
			
			PropertyInfo prop = model.getProperties().get(name); 
			
			String labelStr = (prop.getLabel()!=null&&!prop.getLabel().equals("")) ? prop.getLabel() : name;
				
			Label label = new Label(labelStr + ":");
			if(prop.getDescription()!=null)
				label.setTitle(prop.getDescription()); 
			setWidget(i, 0, label);

			Object val = model.propertyGet(name);

			if (val == null) {
				setWidget(i, 1, new Label("null"));
				continue;
			}

			try {

				final Editor<Object> ed = (Editor<Object>) Types.getInstance().newPropertyEditor(model, name);
				
				if(prop.getEditorConfig()!=null && ed instanceof ConfigurableEditor) {
					((ConfigurableEditor)ed).config(prop.getEditorConfig()); 
				}
				if (ed == null) {
					String msg = "Couldn't create editor for propery " + name + ". ";
					logger.log(Level.SEVERE, msg);
					continue;
				}
				
				//TODO: this make the model modify baddly - commented - events not working...
				// EditorEventManager.getInstance().addValueChangeListener(ed, this); 

				editors.put(name, ed);

				Widget editorWidget = ed.getWidget();
				Widget widget = editorWidget;
				if (!ed.isSimple()) {
					TextBox tb = new TextBox();
					tb.setTitle(name);

					tb.setText(val + "");
					tb.addClickHandler(new ClickHandler() {
						@Override
						public void onClick(ClickEvent event) {
							// String name = ((TextBox)event.getSource()).getTitle();
							
							VerticalPanel vp = new VerticalPanel();
							vp.add(new Label("sub editor: ")); 
							Editor<Object> subed = editors.get(name2);
							Object submodel = getModel().propertyGet(name2);
							Widget subedWidget = subed.getWidget();
							subed.load(submodel);
							System.out.println("AAAA: submodel : "+submodel+", model: "+getModel()+", widget: "+subedWidget+", name: "+name2);
							vp.add(subedWidget);

							FlowPanel fp = new FlowPanel();
							fp.add(new Button("Accept", new ClickHandler() {
								@Override
								public void onClick(ClickEvent event) {
									if (currentDialogBox != null)
										currentDialogBox.hide();
								}
							}));
							fp.add(new Button("Cancel", new ClickHandler() {
								@Override
								public void onClick(ClickEvent event) {
									if (currentDialogBox != null)
										currentDialogBox.hide();
								}
							}));
							vp.add(fp);
							currentDialogBox = GUIUtil.showWidgetDialog(name2, vp);
						}
					});
					widget = tb;
				} else {
					ed.load(val);
				}
				
				if(widget!=null) {					//ignore those editors without repr.
				
				//add a listener to the child editor, and re fire each valuechange event but using this PropertyHaverEditor2 as a event source
				ed.addValueChangeListener(new ValueChangeListener<Object>() {
					@Override
					public void notifyValueChange(EditorValueChangeEvent<Object> evt) {
						EditorEventManager.<T>getInstance().fireValueChangeEvent(PropertyHaverEditor2.this); 
					}
				}); 
				setWidget(i, 1, widget);
				widget.setWidth("100%");

				getCellFormatter().setVerticalAlignment(i, 1, HasVerticalAlignment.ALIGN_TOP);
				getCellFormatter().setVerticalAlignment(i, 0, HasVerticalAlignment.ALIGN_TOP);
				
				
				}
					
			} catch (EditorNotFound e) {
				// TODO: handle exception - editor not found for object
				logger.log(Level.WARNING,
						" exception building editor for prop " + name + " of class " + model.getClass() + " value: "
								+ val + ". Exception cause: " + e.getCause());
				e.printStackTrace();
				throw new RuntimeException(e);
			}
			i++;
		}
	}

//	protected PropertyHaver _getModel() {
//		return model;
//	}

	protected PropertyHaver getModel() {
		return model;
	}

	@Override
	public Widget getWidget() {
		return this;
	}

	@Override
	public void notifyValueChange(EditorValueChangeEvent<Object> evt) {
		//sub editors have been modified. we pass null in the value event property, let them flush by them self if need the value. 
		EditorValueChangeEvent<T> e_ = new EditorValueChangeEvent<T>(this, null, evt);
		EditorEventManager.<T> getInstance().fireValueChangeEvent(this, e_);
	}

	private void removeMySelfAsListener() {
		for (Editor e : editors.values()) {
			EditorEventManager.getInstance().removeValueChangeListener(e, (ValueChangeListener<Object>) this);
		}
	}

	@Override
	public String getEditorId() {
		return EditorManager1.getInstance().getEditorName(this);
	}

	@Override
	public String[] getEditorTypesSupported() {
		return new String[] { EditorFramework1.TYPE_PROPERTYHAVER };
	}

	public Editor getEditorForProperty(String s) {
		return editors.get(s); 
	}
}
